package schedule

import (
	"errors"
	"fmt"
	"strings"
	"sync"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/binocle/logic"
	"github.com/robfig/cron"
)

type ScheduleHandler func(args []string) error

type AgentScheduler interface {
	Schedule(command string, handler ScheduleHandler)
}

type agentSchedule struct {
	observer logic.Observer
	commands sync.Map
}

func newAgentSchedule(observer logic.Observer, subscriber asteroid.Sub) *agentSchedule {
	s := &agentSchedule{
		observer: observer,
		commands: sync.Map{},
	}
	subscriber.On("schedule", func(message asteroid.Message) {
		action := message.Data().(string)
		command := message.GetTarget()
		c := strings.Split(command, " ")
		switch action {
		case "verify":
			_, ok := s.commands.Load(c[0])
			message.Response(ok)
		case "exec":
			f, ok := s.commands.Load(c[0])
			var err error
			if ok {
				if len(c) > 1 {
					err = f.(ScheduleHandler)(c[1:])
				} else {
					err = f.(ScheduleHandler)([]string{})
				}
				if err != nil {
					message.Response(err.Error())
				} else {
					message.Response(nil)
				}
			} else {
				message.Response("no exist")
			}
		}
	})
	return s
}
func (s *agentSchedule) Schedule(command string, handler ScheduleHandler) {
	s.commands.Store(command, handler)
}

type ScheduleType byte

const (
	All ScheduleType = iota
	One
)

type CoreScheduler interface {
	NewScheduleProxy() *Proxy
	SwitchScheduleProxy(proxy *Proxy)
	VerifySchedule(server string, command string, result chan<- map[string]interface{})
}
type coreSchedule struct {
	watcher *logic.WatcherInstance
	proxy   *Proxy
}
type Proxy struct {
	cron           *cron.Cron
	schedule       *coreSchedule
	errorHandler   func(observer string, name string, cron string, command string, err error)
	successHandler func(observer string, name string, cron string, command string)
}

func newCoreSchedule(watcher *logic.WatcherInstance) *coreSchedule {
	s := &coreSchedule{
		watcher: watcher,
	}
	s.proxy = s.NewScheduleProxy()

	return s
}
func (s *coreSchedule) NewScheduleProxy() *Proxy {
	p := &Proxy{
		cron:     cron.New(),
		schedule: s,
	}
	return p
}
func (s *coreSchedule) SwitchScheduleProxy(proxy *Proxy) {
	s.proxy.cron.Stop()
	s.proxy = proxy
	s.proxy.cron.Start()
}
func (s *coreSchedule) VerifySchedule(observer string, command string, result chan<- map[string]interface{}) {
	publisher := s.watcher.Observing(observer)
	if publisher != nil {
		publisher.All().TargetId(command).Data("verify").Callback(func(success bool, data map[string]interface{}) {
			if success {
				result <- data
			} else {
				result <- nil
			}
		}).Send("schedule")
	} else {
		result <- nil
	}
}
func (p *Proxy) SetSchedule(observer string, name string, cron string, command string, t ScheduleType) error {
	switch t {
	case All:
		return p.cron.AddFunc(cron, func() {
			publisher := p.schedule.watcher.Observing(observer)
			if publisher != nil {
				publisher.All().TargetId(command).Data("exec").Callback(func(success bool, data map[string]interface{}) {
					if success {
						message := ""
						for index, info := range data {
							if info != nil {
								message += fmt.Sprintf("node:%s => %s;", index, info)
							}
						}
						if message != "" {
							if p.errorHandler != nil {
								p.errorHandler(observer, name, cron, command, errors.New(message))
							}
						} else {
							if p.successHandler != nil {
								p.successHandler(observer, name, cron, command)
							}
						}
					} else {
						if p.errorHandler != nil {
							p.errorHandler(observer, name, cron, command, errors.New("response empty"))
						}
					}
				}).Send("schedule")
			}
		})
	case One:
		return p.cron.AddFunc(cron, func() {
			publisher := p.schedule.watcher.Observing(observer)
			if publisher != nil {
				publisher.Auto().TargetId(command).Data("exec").CallbackTo(func(success bool, data interface{}) {
					if success {
						if data != nil {
							if p.errorHandler != nil {
								p.errorHandler(observer, name, cron, command, errors.New(data.(string)))
							}
						} else {
							if p.successHandler != nil {
								p.successHandler(observer, name, cron, command)
							}
						}
					} else {
						if p.errorHandler != nil {
							p.errorHandler(observer, name, cron, command, errors.New("response empty"))
						}
					}
				}).Send("schedule")
			}
		})
	}
	return nil
}

func (p *Proxy) OnError(fn func(observer string, name string, cron string, command string, err error)) {
	p.errorHandler = fn
}

func (p *Proxy) OnSuccess(fn func(observer string, name string, cron string, command string)) {
	p.successHandler = fn
}
